#include <iostream>
#include <fstream>
#include <stack>

#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>

#include "cluster/bwt/bwt.h"

#include "step/mtf/mtf.h"
#include "step/rle/rle.h"

#include "storage/huffman/huffman.h"

#include "include/hzstream.h"


using namespace std;
using namespace bwt;
using namespace mtf;
using namespace rle;
using namespace hfm;

const static int alphabet_size = 256;


void printUsage(const char * exe) {
	printf( "USAGE:\n"
			"\t%s [options] <input_filename>\n",
			exe);
}

void printHelp(void) {
	cout << "Possible options: " << endl;
	cout << "d -- decoding data" << endl;
	cout << "e -- encoding data" << endl;
	cout << "h -- print this help" << endl;
	cout << "i <filename> -- input file" << endl;
	cout << "o <filename> -- output file" << endl;
	cout << "s <size> -- BWT block size" << endl;
	cout << "v -- verbose mode" << endl;
	cout << "1 -- MTF + RLE + Huffman " << endl;
	cout << "2 -- MTF + Huffman" << endl;
}

/*
 * Entry point for the project
 */
int main(int argc, char** argv) {

	char* in_filename = NULL;
	char* out_filename = NULL;
	fstream input;
	fstream output;
	bool verbose = false;
	bool decoding = false, encoding = false;
	int alg_type = 0;
	int32_t data_size = 0;
	uint8_t *buf_1;

	BWTencoder *encoder;
	BWTdecoder *decoder;


	MTF* mtf;
	RLE* rle;


	HFM hfm;
	hzstream hz;

	if (argc < 2) {
		printUsage(argv[0]);
		return EXIT_SUCCESS;
	}

	// Parsing console parameters
	int c;
	while ( (c = getopt(argc, argv, "dehi:o:v12")) != -1) {
		switch (c) {
		case 'd':
			decoding = true;
			break;
		case 'e':
			encoding = true;
			break;
		case 'i':
			in_filename = optarg;
			break;
		case 'o':
			out_filename = optarg;
			break;
		case 'v':
			verbose = true;
			break;
		case '1':
			//cout << "MTF + RLE + Huffman" << endl;
			mtf = new MTF();
			rle = new RLE();
			alg_type = 1;
			break;
		case '2':
			//cout << "MTF + Huffman" << endl;
			mtf = new MTF();
			alg_type = 2;
			break;
		case '?':
		case 'h':
			printHelp();
			return EXIT_SUCCESS;
		default:
			printf("Unknown argument: %c!\n", c);
			return EXIT_FAILURE;
			break;
		}
	}
	if (optind < argc) {
		printf("Too many input files specified!\n");
		return EXIT_FAILURE;
	}

	//Checking if all required parameters are set up

	input.open(in_filename, fstream::in|fstream::binary);
	if(input.fail()){
		cout << "Couldn't open input file: " << in_filename << endl;
		return EXIT_FAILURE;
	} else{
		input.seekg(0, fstream::end);
		data_size = input.tellg();
		if(data_size == -1){
			cout << "Couldn't get input file size: " << in_filename << endl;
			return EXIT_FAILURE;
		}
		input.seekg(0, fstream::beg);
	}

	output.open(out_filename, fstream::out|fstream::trunc|fstream::binary);
	if(output.fail()){
		cout << "Couldn't open output file: " << out_filename << endl;
		return EXIT_FAILURE;
	}

	if(alg_type == 0){
		cout << "You have to choose working mode" << endl;
		printHelp();
		return EXIT_FAILURE;
	}

	if((!encoding && !decoding) || (decoding && encoding)){
		cout <<"You must choose one working mode" << endl;
		printHelp();
		return EXIT_FAILURE;
	}


	if(verbose){
		cout << "Program call summary: " << endl;
		cout << "Input filename = " << in_filename << endl;
		cout << "Input data size = " << data_size << endl;
		cout << "Output filename = " << out_filename << endl;
		cout << "Algorithm type: ";
		switch(alg_type){
			case 1:
				cout << "MTF + RLE + Huffman" << endl;
				break;
			case 2:
				cout << "MTF + Huffman" << endl;
				break;
		}
	}

	//Executing encoding/decoding
	if(encoding){

		buf_1 = new uint8_t[data_size];

		input.read(reinterpret_cast<char *>(buf_1), data_size);


		hz.in_buf = buf_1;
		hz.in_size = data_size;
		
		encoder = new bwt::BWTencoder(hz.in_size);
		encoder->encode(&hz);

		hz.in_size = hz.out_size;
		hz.in_buf = new uint8_t[hz.in_size];
		hz.in_buf = hz.out_buf;

		switch(alg_type){
			case 1:
				cout<<"MTF + RLE + Huffman encoding"<<endl;
				mtf->encode(&hz);

				/*delete[] hz.in_buf;
		//		hz.in_buf = hz.out_buf;
		//		hz.in_size = hz.out_size;
		hz.in_size = hz.out_size;
		hz.in_buf = new uint8_t[hz.in_size];
		hz.in_buf = hz.out_buf;
				*/

				rle->encode(&hz);
				break;
			case 2:
				cout<<"MTF + Huffman encoding"<<endl;
				mtf->encode(&hz);
				break;
		}

		//hz.in_buf = hz.out_buf;
		//hz.in_size = hz.out_size;
		hz.in_size = hz.out_size;
		hz.in_buf = new uint8_t[hz.in_size];
		hz.in_buf = hz.out_buf;

		hfm.encode(&hz);


		output.write((char*)hz.out_buf, hz.out_size);

		hz.in_buf = NULL;
		hz.out_buf = NULL;
	}

	if(decoding){

		buf_1 = new uint8_t[data_size];
		input.read(reinterpret_cast<char *>(buf_1), data_size);

		hz.in_buf = buf_1;
		hz.in_size = data_size;
		cout<<hz.in_size<<endl;

		hfm.decode(&hz);

		//hz.in_buf = hz.out_buf;
		//hz.in_size = hz.out_size;

		hz.in_size = hz.out_size;
		hz.in_buf = new uint8_t[hz.in_size];
		hz.in_buf = hz.out_buf;

		switch(alg_type){
			case 1:
				cout<<"MTF + RLE + Huffman decoding"<<endl;
				rle->decode(&hz);

		//		hz.in_buf = hz.out_buf;
		//		hz.in_size = hz.out_size;
		hz.in_size = hz.out_size;
		hz.in_buf = new uint8_t[hz.in_size];
		hz.in_buf = hz.out_buf;

				mtf->decode(&hz);
				break;
			case 2:
				cout<<"MTF + Huffman decoding"<<endl;
				mtf->decode(&hz);
				break;
		}

		//hz.in_buf = hz.out_buf;
		//hz.in_size = hz.out_size;
		hz.in_size = hz.out_size;
		hz.in_buf = new uint8_t[hz.in_size];
		hz.in_buf = hz.out_buf;

		decoder = new BWTdecoder(hz.in_size);
		decoder->decode(&hz);

		output.write((char*)hz.out_buf,hz.out_size);

		delete[] hz.in_buf;
		delete[] hz.out_buf;
	}

	return EXIT_SUCCESS;
}

